package com.chinaunicom.emergency.config;

import com.chinaunicom.emergency.config.translator.OAuth2WebResponseExceptionTranslator;
import com.chinaunicom.emergency.model.YjglUser;
import com.chinaunicom.emergency.provider.LoginAuthenticationProvider;
import com.chinaunicom.emergency.repository.UsersRepository;
import com.chinaunicom.emergency.security.BCryptPasswordEncoder;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.sql.DataSource;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
@Log4j2
@EnableAuthorizationServer
public class ServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Qualifier("userDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    @Autowired
    private UsersRepository usersRepository;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 配置token获取和验证时的策略
        security.tokenKeyAccess("permitAll()")//允许所有资源服务器访问公钥端点（/oauth/token_key）
                .checkTokenAccess("permitAll()")//允许所有用户访问令牌解析端点（/oauth/check_token）
                .allowFormAuthenticationForClients(); // 允许客户端发送表单来进行权限认证来获取令牌

        //只允许验证用户访问令牌解析端点（/oauth/check_token）
//      .tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
    }


    @Bean
    AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return daoAuhthenticationProvider().authenticate(authentication);
            }
        };
        return authenticationManager;
    }

    /**
     * token增强,添加一些元信息
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer(){
        return new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                if (accessToken instanceof DefaultOAuth2AccessToken){
                    DefaultOAuth2AccessToken token= (DefaultOAuth2AccessToken) accessToken;
                    User user = (User) authentication.getPrincipal();
                    if(null != user){
                        Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
                        additionalInformation.put("user_name",user.getUsername());
                        YjglUser yjglUser = usersRepository.findByUserNameAndIsSoftDel(user.getUsername(),0);
                        if(null != yjglUser && StringUtils.isNotBlank(yjglUser.getRealName())){
                            additionalInformation.put("real_name",yjglUser.getRealName());
                        }
                        token.setAdditionalInformation(additionalInformation);
                    }
                }
                return accessToken;
            }
        };
    }



    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        LoginAuthenticationProvider loginAuthenticationProvider = new LoginAuthenticationProvider();
        loginAuthenticationProvider.setUserDetailsService(userDetailsService);
        loginAuthenticationProvider.setHideUserNotFoundExceptions(false);
        loginAuthenticationProvider.setPasswordEncoder(new PasswordEncoder(){
                @Override
                public String encode(CharSequence charSequence) {
                    // 加密
                    return new BCryptPasswordEncoder().encode(charSequence);
                }
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    // 密码校验
                    boolean flag = new BCryptPasswordEncoder().matches(charSequence,s);
                    return flag;
                }
            });
        return loginAuthenticationProvider;
    }

    /**
     *          配置密码加密对象（解密时会用到PasswordEncoder的matches判断是否正确）
     *          用户的password和客户端clientSecret用到，所以存的时候存该bean encode过的密码
     * @return
     */
    @Bean // password加密的方式 相当于把PasswordEncoder类对象 注册到容器中
    public PasswordEncoder passwordEncoder() {
        // 加密方式
        return new PasswordEncoder(){
            @Override
            public String encode(CharSequence charSequence) {
                // 加密
                return new BCryptPasswordEncoder().encode(charSequence);
            }
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                // 密码校验
                boolean flag = new BCryptPasswordEncoder().matches(charSequence,s);
                log.info("密码校验结果：" + flag);
                return flag;
            }
        };
    }
    /**
     * 声明 ClientDetails实现
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /*clients.inMemory()
                .withClient("client")
                // secret密码配置从 Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写
                *//*
                 *   当前版本5新增支持加密方式：
                 *   bcrypt - BCryptPasswordEncoder (Also used for encoding)
                 *   ldap - LdapShaPasswordEncoder
                 *   MD4 - Md4PasswordEncoder
                 *   MD5 - new MessageDigestPasswordEncoder("MD5")
                 *   noop - NoOpPasswordEncoder
                 *   pbkdf2 - Pbkdf2PasswordEncoder
                 *   scrypt - SCryptPasswordEncoder
                 *   SHA-1 - new MessageDigestPasswordEncoder("SHA-1")
                 *   SHA-256 - new MessageDigestPasswordEncoder("SHA-256")
                 *   sha256 - StandardPasswordEncoder
                 *//*
                .secret("{noop}secret")
                .scopes("all")
                .authorizedGrantTypes("authorization_code", "password", "refresh_token")
                .autoApprove(true);*/
        //从数据库获取client_id和client_secret,如有加密,client_secret值需要在数据库加密存储
        clients.jdbc(dataSource);
    }

    @Bean
    public TokenStore tokenStore() {
        return new MyRedisTokenStore(redisConnectionFactory);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置tokenStore，保存到redis缓存中
        endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancer())
                .authenticationManager(authenticationManager)//开启密码授权类型
                // 不添加userDetailsService，刷新access_token时会报错
                .userDetailsService(userDetailsService)
                //配置token有效期时间等信息
                .tokenServices(tokenServices())
                //自定义 /oauth/token 异常处理
                .exceptionTranslator(new OAuth2WebResponseExceptionTranslator());
    }


    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);//支持刷新token
        tokenServices.setReuseRefreshToken(true);
        tokenServices.setTokenEnhancer(tokenEnhancer());
        tokenServices.setClientDetailsService(clientDetails());
        tokenServices.setAccessTokenValiditySeconds(60*60*24*7); // token有效期自定义设置，默认7天
        tokenServices.setRefreshTokenValiditySeconds(60*60*24*15);//默认3天，这里修改15天
        return tokenServices;
    }
}
